#region Copyright Notices
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
// 
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#endregion

using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Reflection;
using System.Globalization;

namespace Yarr
{
	/// <summary>
	/// Provides a hashtable cache of common language types in order to
	/// avoid repetitive reflection. Provides features for finding types
	/// with unqualified names.
	/// </summary>
	internal static class TypeCache
	{
		private const int CAPACITY = 1000;
		private static Dictionary<String, Type> typeTable = new Dictionary<string,Type>(CAPACITY);
		private static List<String> usingTable = new List<string>();

		/// <summary>
		/// Clears the cache and resets its state to default values
		/// </summary>
		public static void Clear() 
		{
            lock (typeTable)
            {
                if (typeTable.Count > 0)
                    typeTable = new Dictionary<String, Type>(CAPACITY);
            }

            lock (usingTable)
            {
                if (usingTable.Count > 0)
                    usingTable = new List<String>(CAPACITY);
            }

            Using("Yarr");
            Using("System");
            Using("System.Collections.Generic");
            Using("System.Collections");
            Using("System.IO");
            Using("System.Text");
            Using("System.Linq");
            Using("System.XML");
		}

        public static void ClearNulls()
        {
            List<String> entriesToRemove = new List<string>();

            lock (typeTable)
            {
                foreach (string key in typeTable.Keys)
                    if (typeTable[key] == null)
                        entriesToRemove.Add(key);

                foreach (string key in entriesToRemove)
                    typeTable.Remove(key);
            }
        }

		static TypeCache()
		{
			Clear();
		}

		/// <summary>
		/// Finds a type given its fully qualified or unqualified name
		/// Takes advantage of the cache to speed up lookups
		/// </summary>
		/// <param name="type"></param>
		/// <returns></returns>
		public static Type FindType(string type) 
		{
            if (String.IsNullOrEmpty(type))
                return null;

            string lcType = type.ToLower(CultureInfo.InvariantCulture);

            return SearchType(lcType);
		}

        /// <summary>
        /// Finds a type given its fully qualified or unqualified name
        /// Takes advantage of the cache to speed up lookups. Supports
        /// Generic types
        /// </summary>
        public static Type FindType(string typeName, Type[] typeArray)
        {
            if (typeArray == null || typeArray.Length == 0)
                return FindType(typeName);

            Type genType = FindType(String.Format(CultureInfo.InvariantCulture, 
                                                "{0}`{1}", typeName, typeArray.Length));

            if (genType != null)
            {
                StringBuilder sb = new StringBuilder(genType.ToString());
                sb.Append('[');
                lock (typeArray)
                {
                    for (int ix = 0; ix < typeArray.Length; ++ix)
                        sb.AppendFormat("{0}{1}", typeArray[ix].ToString(),
                                                  (ix == typeArray.Length - 1) ? "" : ",");
                }
                sb.Append(']');
                string resolvedTypeName = sb.ToString();

                lock (typeTable)
                {
                    if (typeTable.ContainsKey(resolvedTypeName))
                        return typeTable[resolvedTypeName];
                }

                genType = genType.MakeGenericType(typeArray);

                lock (typeTable)
                {
                    typeTable[resolvedTypeName] = genType;
                }
            }

            return genType;
        }

        public static void Using(string name) 
		{
            string usingName = name.ToLower(CultureInfo.InvariantCulture);

            lock (usingTable)
            {
                if (!usingTable.Contains(usingName))
                    usingTable.Add(usingName);
            }

            ClearNulls();
		}

		private static Type SearchType(string typeName) 
		{
			// Look up the type in the current assembly
			Type type = Lookup(typeName);

			// Try to use the using directives to guess the namespace ..
            if (type == null)
            {
                lock (usingTable)
                {
                    foreach (string name in usingTable)
                    {
                        type = Lookup(string.Format(CultureInfo.InvariantCulture, 
                                                    "{0}.{1}", name, typeName));
                        if (type != null)
                            break;
                    }
                }
            }

			return type;
		}

        private static Type Lookup(string typeName)
        {
            // Check the type cache first
            lock (typeTable)
            {
                if (typeTable.ContainsKey(typeName))
                    return typeTable[typeName];
            }

            // Look up the type in the current assembly
            Type type = Type.GetType(Normalize(typeName), false, true);

            if (type == null)
            {
                // Look up the type in all loaded assemblies
                foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
                {
                    type = assembly.GetType(typeName, false, true);
                    if (type != null)
                        break;
                }
            }

            lock (typeTable)
            {
                typeTable[typeName] = type;
            }

            return type;
        }

        private static string Normalize(string typeName)
        {
            if (!typeName.Contains("*"))
                return typeName;

            int pos = typeName.IndexOf("*");

            StringBuilder normalizedName = new StringBuilder();
            normalizedName.Append(typeName.Substring(0, pos));

            while (pos < typeName.Length)
            {
                if (typeName[pos] != '*')
                    return typeName;

                if (pos + 1 == typeName.Length || typeName[pos + 1] == '*')
                {
                    normalizedName.Append("[]");
                    ++pos;
                    continue;
                }

                ++pos;
                if (typeName[pos] < '1' || typeName[pos] > '9')
                    return typeName;

                int rank = typeName[pos++] - '0';
                normalizedName.Append('[');
                while (rank-- > 1)
                    normalizedName.Append(',');
                normalizedName.Append(']');
            }

            return normalizedName.ToString();
        }
	}
}
